use std::ffi::{OsString, OsStr};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::fs::{Metadata, read_dir};
use std::os::unix::prelude::MetadataExt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};

use tracing::{debug, error, info, trace, warn};

use serde::{Deserialize, Serialize};
use fuse3::{FileType};
use tokio::sync::RwLock;
use xor_name::XorName;
use async_recursion::async_recursion;

use crate::error::FileSystemError;

#[derive(Clone, Debug)]
struct VPath {
    inner: Vec<OsString>,
    next: usize,
}

impl VPath {
    pub fn peel(&mut self) -> (&OsStr, bool, PathBuf) {
        let out = self.inner[self.next].as_os_str();
        self.next = self.next + 1;
        let mut current = PathBuf::new();
        for i in 0..self.next {
            current.push(self.inner[i].clone());
        }
        (out, self.next == self.inner.len(), current)
    }
}

impl From<&Path> for VPath {
    fn from(path: &Path) -> Self {
        let vec = path.iter().map(|s| s.to_os_string()).collect();
        VPath {
            inner: vec,
            next: 0,
        }
    }
}

impl From<VPath> for PathBuf {
    fn from(p: VPath) -> Self {
        let mut path = PathBuf::new();
        for p in p.inner.iter() {
            path.push(p)
        }
        path
    }
}

impl Display for VPath {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let mut path = PathBuf::new();
        for p in self.inner.iter() {
            path.push(p)
        }
        write!(f, "{}", path.display())
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct Attribute {
    pub size: u64,
    pub blocks: u64,
    pub nlink: u32,
    pub rdev: u32,
    pub blksize: u32,
    pub folder: bool,
    pub atime: i64,
    pub mtime: i64,
    pub ctime: i64,
}

impl From<std::fs::Metadata> for Attribute {
    fn from(metadata: Metadata) -> Self {
        Attribute {
            size: metadata.size(),
            blocks: metadata.blocks(),
            nlink: metadata.nlink() as u32,
            rdev: metadata.rdev() as u32,
            blksize: metadata.blksize() as u32,
            folder: metadata.is_dir(),
            atime: metadata.atime(),
            mtime: metadata.mtime(),
            ctime: metadata.ctime(),
        }
    }
}

#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
pub enum Location {
    Local(usize),
    Remote(XorName),
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum NodeAttr {
    Directory,
    File(Location, Attribute)
}

impl NodeAttr {
    pub fn file_type(&self) -> FileType {
        match self {
            NodeAttr::Directory => FileType::Directory,
            NodeAttr::File(..) => FileType::RegularFile,
        }
    }
}

pub struct Node {
    id: u64,
    name: OsString,
    attr: NodeAttr,
    path: PathBuf,
    children: Vec<Arc<RwLock<Node>>>,
}

impl Node {
    pub fn root() -> Self {
        Node {
            id: 1,
            name: OsString::from("/"),
            path: PathBuf::from("/"),
            attr: NodeAttr::Directory,
            children: Vec::new(),
        }
    }

    pub fn node(id: u64, name: &OsStr, path: PathBuf, attr: NodeAttr) -> Self {
        Node {
            id,
            name: name.to_os_string(),
            path,
            attr,
            children: Vec::new(),
        }
    }

    pub fn folder(id: u64, name: &OsStr, path: PathBuf) -> Self {
        Node {
            id,
            name: name.to_os_string(),
            path,
            attr: NodeAttr::Directory,
            children: Vec::new(),
        }
    }

    #[allow(dead_code)]
    pub fn file(id: u64, location: Location, name: &OsStr, path: PathBuf, attr: Attribute) -> Self {
        Node {
            id,
            name: name.to_os_string(),
            path,
            attr: NodeAttr::File(location, attr),
            children: Vec::new(),
        }
    }

    #[allow(dead_code)]
    pub fn local(id: u64, root: usize, name: &OsStr, path: PathBuf, attr: Attribute) -> Self {
        Node {
            id,
            name: name.to_os_string(),
            path,
            attr: NodeAttr::File(Location::Local(root), attr),
            children: Vec::new(),
        }
    }

    #[allow(dead_code)]
    pub fn remote(id: u64, node: XorName, name: &OsStr, path: PathBuf, attr: Attribute) -> Self {
        Node {
            id,
            name: name.to_os_string(),
            path,
            attr: NodeAttr::File(Location::Remote(node), attr),
            children: Vec::new(),
        }
    }

    pub fn id(&self) -> u64 {
        self.id
    }

    #[allow(dead_code)]
    pub fn name(&self) -> OsString {
        self.name.clone()
    }

    pub fn path(&self) -> PathBuf {
        self.path.clone()
    }

    pub fn node_attr(&self) -> NodeAttr {
        self.attr.clone()
    }

    #[allow(dead_code)]
    pub fn node_kind(&self) -> FileType {
        self.attr.file_type()
    }

    pub fn rename(&mut self, name: &OsStr, parent: &Path) {
        self.name = name.to_os_string();
        self.path = parent.join(name);
    }

    pub fn add_child(&mut self, node: Arc<RwLock<Node>>) {
        self.children.push(node)
    }

    pub async fn find_child(&self, name: &OsStr) -> Option<Arc<RwLock<Node>>> {
        for v in &self.children {
            if v.read().await.name.eq(name) {
                return Some(v.clone())
            }
        }
        None
    }

    pub async fn have_child(&self, name: &OsStr) -> bool {
        for v in &self.children {
            if v.read().await.name.eq(name) {
                return true
            }
        }
        false
    }

    pub async fn remove_child(&mut self, name: &OsStr) -> Result<Arc<RwLock<Node>>, FileSystemError> {
        let mut index = 0usize;
        for v in &self.children {
            index = index + 1;
            let read = v.read().await;
            if read.name.eq(name) {
                break
            }
        }
        if index > 0 && index <= self.children.len() {
            let find = self.children.remove(index - 1);
            Ok(find.clone())
        } else {
            warn!("remove child {}, {}, {}", index, self.id, self.children.len());
            Err(FileSystemError::NameNotFoundError(name.to_os_string()))
        }
    }

    pub fn children(&self) -> Vec<Arc<RwLock<Node>>> {
        self.children.clone()
    }

    pub fn set_attributes(&mut self, attribute: Attribute) -> Result<Option<(PathBuf, Attribute)>, FileSystemError> {
        if let NodeAttr::File(location, attr) = &self.attr {
            if !attribute.eq(attr) {
                let attr = attr.clone();
                self.attr = NodeAttr::File(location.clone(), attribute.clone());
                Ok(Some((self.path.clone(), attr)))
            } else {
                Ok(None)
            }
        } else {
            Err(FileSystemError::NodeNotFileError(self.path.clone()))
        }
    }
}


impl fmt::Debug for Node {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.attr {
            NodeAttr::Directory => {
                f.debug_struct("Node")
                    .field("id", &self.id)
                    .field("name", &self.name)
                    .field("children", &self.children)
                    .finish()
            }
            NodeAttr::File(location, attribute) => {
                f.debug_struct("Node")
                    .field("id", &self.id)
                    .field("name", &self.name)
                    .field("location", &location)
                    .field("attribute", &attribute)
                    .finish()
            }
        }
    }
}

#[derive(Debug)]
pub struct NTree {
    generator: AtomicU64,
    length: AtomicUsize,
    root: Arc<RwLock<Node>>,
}

impl NTree {
    pub async fn load(paths: Vec<PathBuf>) -> Result<Self, FileSystemError> {
        let mut tree = NTree::root();
        for (index, path) in paths.iter().enumerate() {
            info!("load path: {}", path.display());
            if path.is_dir() {
                let prefix = PathBuf::from("/");
                load(index, &mut tree, prefix, path.clone()).await
            }
        }
        trace!("{:#?}", tree);
        Ok(tree)
    }

    pub fn root() -> Self {
        NTree {
            generator: AtomicU64::new(10),
            length: AtomicUsize::new(0),
            root: Arc::new(RwLock::new(Node::root())),
        }
    }

    pub async fn store(&mut self, path: &Path, attr: NodeAttr) -> Option<u64> {
        let mut p = VPath::from(path);
        let _ = p.peel();
        let store = put(p, self.root.clone(), &self.generator, attr).await;
        if let Some(id) = store {
            self.length.fetch_add(1, Ordering::SeqCst);
            Some(id)
        } else {
            None
        }
    }

    pub async fn lookup(&self, parent: u64, name: &OsStr) -> Option<Arc<RwLock<Node>>> {
        let p = locate(parent, self.root.clone()).await;
        if let Some( node) = p {
            node.read().await.find_child(name).await
        } else {
            None
        }
    }

    pub async fn r#match(&self, path: &Path) -> Option<Arc<RwLock<Node>>> {
        let np = VPath::from(path);
        find(np, self.root.clone()).await
    }

    pub async fn locate(&self, id: u64) -> Option<Arc<RwLock<Node>>> {
        locate(id, self.root.clone()).await
    }

    pub async fn children(&self, parent: u64) -> Option<Vec<Arc<RwLock<Node>>>> {
        let node = locate(parent, self.root.clone()).await;
        if let Some(node) = node {
            trace!("read children local, parent {}, path {}", parent, node.read().await.path.display());
            let mut vec = Vec::new();
            for v in node.read().await.children() {
                vec.push( v.clone());
            }
            Some(vec)
        } else {
            None
        }
    }

    pub async fn remove_node(&self, parent: u64, name: &OsStr) -> Result<(Arc<RwLock<Node>>, Arc<RwLock<Node>>), FileSystemError> {
        if let Some(f) = locate(parent, self.root.clone()).await {
            let node = f.write().await.remove_child(name).await?;
            Ok((f.clone(), node))
        } else {
            Err(FileSystemError::NodeNotFoundError(parent))
        }
    }

    pub async fn remove_path(&self, path: &Path) -> Result<(Arc<RwLock<Node>>, Arc<RwLock<Node>>), FileSystemError> {
        if let Some(parent) =  path.parent() {
            let p = find(parent.into(), self.root.clone()).await;
            if let Some(f) = p {
                let name = path.file_name().ok_or(FileSystemError::PathNotFoundError(path.to_path_buf()))?;
                let node = f.write().await.remove_child(name).await?;
                Ok((f.clone(), node))
            } else {
                Err(FileSystemError::PathNotFoundError(path.to_path_buf()))
            }
        } else {
            Err(FileSystemError::NodeNotFileError(path.to_path_buf()))
        }
    }

    pub async fn rename_node(&self, parent: u64, name: &OsStr, new_parent: u64, new_name: &OsStr) -> Result<(PathBuf, PathBuf, Arc<RwLock<Node>>), FileSystemError> {
        if parent == new_parent {
            if let Some(f) = locate(parent, self.root.clone()).await {
                let path = f.read().await.path();
                let child = f.read().await.find_child(name).await;
                if let Some(c) = child {
                    let source = c.read().await.path();
                    c.write().await.rename(new_name, &path);
                    let target = c.read().await.path();
                    Ok((source, target, c))
                } else {
                    Err(FileSystemError::NameNotFoundError(name.to_os_string()))
                }
            } else {
                Err(FileSystemError::NodeNotFoundError(parent))
            }
        } else {
            if let Some(f) = locate(parent, self.root.clone()).await {
                let path = f.read().await.path();
                let find = f.write().await.remove_child(name).await?;
                let target = locate(new_parent, self.root.clone()).await;
                if let Some(node) = target {
                    let source = find.read().await.path();
                    find.write().await.rename(new_name, &path);
                    let target = find.read().await.path();
                    node.write().await.add_child(find.clone());
                    Ok((source, target, find))
                } else {
                    Err(FileSystemError::NodeNotFoundError(new_parent))
                }
            } else {
                Err(FileSystemError::NodeNotFoundError(parent))
            }
        }
    }

    pub async fn rename_path(&self, source: &Path, target: &Path) -> Result<Arc<RwLock<Node>>, FileSystemError> {
        let s_parent = source.parent().ok_or(FileSystemError::PathNotFoundError(source.to_path_buf()))?;
        let t_parent = target.parent().ok_or(FileSystemError::PathNotFoundError(target.to_path_buf()))?;
        let s_name = source.file_name().ok_or(FileSystemError::PathNotFoundError(source.to_path_buf()))?;
        let t_name = target.file_name().ok_or(FileSystemError::PathNotFoundError(target.to_path_buf()))?;
        let find_parent = find(VPath::from(s_parent), self.root.clone()).await;
        if let Some(parent) = find_parent {
            let path = parent.read().await.path();
            if s_parent.eq(t_parent) {
                if let Some(c) = parent.read().await.find_child(s_name).await{
                    c.write().await.rename(t_name, &path);
                    Ok(c)
                } else {
                    Err(FileSystemError::NameNotFoundError(s_name.to_os_string()))
                }
            } else {
                let moved = parent.write().await.remove_child(s_name).await?;
                let target_parent = find(VPath::from(t_parent), self.root.clone()).await;
                if let Some(t) = target_parent {
                    let path = t.read().await.path();
                    moved.write().await.rename(t_name, &path);
                    t.write().await.add_child(moved.clone());
                    Ok(moved)
                } else {
                    Err(FileSystemError::PathNotFoundError(t_parent.to_path_buf()))
                }
            }
        } else {
            Err(FileSystemError::PathNotFoundError(s_parent.to_path_buf()))
        }
    }

    pub async fn node_attribute(&self, node: u64, attribute: Attribute) -> Result<Option<(PathBuf, Attribute)>, FileSystemError> {
        let find = locate(node, self.root.clone()).await;
        if let Some(find) = find {
            find.write().await.set_attributes(attribute)
        } else {
            Err(FileSystemError::NodeNotFoundError(node))
        }
    }

    pub async fn path_attribute(&mut self, path: &Path, attribute: Attribute) -> Result<Option<(PathBuf, Attribute)>, FileSystemError> {
        if let Some(find) = find(path.into(), self.root.clone()).await {
            find.write().await.set_attributes(attribute)
        } else {
            Err(FileSystemError::PathNotFoundError(path.to_path_buf()))
        }
    }

    pub async fn to_array(&self) -> Vec<(PathBuf, Attribute)> {
        let mut array = Vec::new();
        flatten(self.root.clone(), &mut array).await;
        array
    }
}

#[async_recursion]
async fn load(root: usize, tree: &mut NTree, prefix: PathBuf, dir: PathBuf) {
    for entry in read_dir(dir).unwrap() {
        let entry = entry.unwrap();
        let path = entry.path();
        let mut p = prefix.clone();
        p.push(path.file_name().unwrap());

        let metadata = entry.metadata().unwrap();
        let attr = if metadata.is_dir() {
            NodeAttr::Directory
        } else {
            NodeAttr::File(Location::Local(root), metadata.into())
        };
        if let Some(id) = tree.store(&p, attr).await {
            debug!("load node: {} id: {}", path.display(), id);
        } else {
            warn!("load path {} error, node is exists.", p.display())
        }
        if path.is_dir() {
            load(root, tree, p, path).await
        }
    }
}

#[async_recursion]
async fn find(mut path: VPath, node: Arc<RwLock<Node>>) -> Option<Arc<RwLock<Node>>> {
    let (name, finish, _p) = path.peel();
    if node.read().await.name.eq(name) {
        if finish{
            Some(node.clone())
        } else {
            for v in node.read().await.children.iter() {
                let f = find(path.clone(), v.clone()).await;
                if let Some(r) = f {
                    return Some(r)
                }
            }
            None
        }
    } else {
        None
    }
}

#[async_recursion]
async fn locate(id: u64, node: Arc<RwLock<Node>>) -> Option<Arc<RwLock<Node>>> {
    let inode = node.read().await.id();
    if inode == id {
        return Some(node)
    } else {
        for v in node.read().await.children.iter() {
            let p = locate(id, v.clone()).await;
            if let Some(f) = p {
                return Some(f.clone())
            }
        }
        None
    }
}

#[async_recursion]
async fn put(mut path: VPath, node: Arc<RwLock<Node>>, generator: &AtomicU64, attr: NodeAttr) -> Option<u64> {
    let (name, finish, p) = path.peel();
    if finish {
        let have = node.read().await.have_child(name).await;
        if !have {
            let id = generator.fetch_add(1, Ordering::SeqCst);
            let nn = Node::node(id, name, p, attr);
            node.write().await.add_child(Arc::new(RwLock::new(nn)));
            Some(id)
        } else {
            None
        }
    } else {
        let f = node.read().await.find_child(name).await;
        match f {
            None => {
                let id = generator.fetch_add(1, Ordering::SeqCst);
                let nn = Arc::new(RwLock::new(Node::folder(id,  name, p)));
                let r = put(path, nn.clone(), generator, attr).await;
                node.write().await.add_child(nn);
                r
            },
            Some(id) => put(path, id, generator,  attr).await
        }
    }
}

#[async_recursion]
async fn flatten(node: Arc<RwLock<Node>>, array: &mut Vec<(PathBuf, Attribute)>) {
    let attr = node.read().await.node_attr();
    match attr {
        NodeAttr::Directory => {
            let children = node.read().await.children();
            for v in children.iter() {
                flatten(v.clone(), array).await;
            }
        }
        NodeAttr::File(location, attribute) => {
            if let Location::Local(_) = location {
                array.push((node.read().await.path(), attribute))
            }
        }
    }
}